---
title: Todos
slug: /examples/todos
description:
  In this example, we will build the classic TodoMVC, which has become the
  default way of showcasing the capabilities of a state-management library.
---

import { PubBadge } from '../../../src/components/Shield';

In this example, we will build the classic [TodoMVC](http://todomvc.com/), which
has become the default way of showcasing the capabilities of a state-management
library.

:::info

See the complete code
[here](https://github.com/mobxjs/mobx.dart/tree/main/mobx_examples/lib/todos).

:::

## Creating the Observable state

We first start by defining the Observable state of our application. This is
usually the first step in modeling your application. In this case, we have a
`TodoList` managing a list of `Todo` instances.

A `Todo` is defined by a simple class like the following.

### Todo class

```dart
import 'package:mobx/mobx.dart';

part 'todo.g.dart';

class Todo = _Todo with _$Todo;

abstract class _Todo with Store {
  _Todo(this.description);

  @observable
  String description = '';

  @observable
  bool done = false;
}
```

You should now be familiar with the little boilerplate here.

> If the boilerplate seems new, take a look at the
> [Counter example](/examples/counter)

### TodoList class

Next comes the `TodoList`, which manages a list of `Todo`s. The core-state of
`TodoList` includes:

- [x] a list of `Todo`s
- [x] a `filter` that tracks the visibility-filter applied on the list of todos

This can be seen with the `@observable` properties.

```dart
import 'package:mobx/mobx.dart';

part 'todo_list.g.dart';

enum VisibilityFilter { all, pending, completed }

class TodoList = _TodoList with _$TodoList;

abstract class _TodoList with Store {
  @observable
  ObservableList<Todo> todos = ObservableList<Todo>();

  @observable
  VisibilityFilter filter = VisibilityFilter.all;

}
```

**Computed Properties**

To render a useful UI, we also need a bunch of _derived (aka computed)_
properties. These properties are not inherent to the `TodoList` but derived from
the core-state. Our UI looks like below, so you can identify the mapping of the
_computed properties_ to the various widgets.

import todosImage from './todos.png';

<img src={todosImage} width={300} />

```dart
class TodoList = _TodoList with _$TodoList;

abstract class _TodoList with Store {
  // ...

  @computed
  ObservableList<Todo> get pendingTodos =>
      ObservableList.of(todos.where((todo) => todo.done != true));

  @computed
  ObservableList<Todo> get completedTodos =>
      ObservableList.of(todos.where((todo) => todo.done == true));

  @computed
  bool get hasCompletedTodos => completedTodos.isNotEmpty;

  @computed
  bool get hasPendingTodos => pendingTodos.isNotEmpty;

  @computed
  String get itemsDescription {
    if (todos.isEmpty) {
      return "There are no Todos here. Why don't you add one?.";
    }

    final suffix = pendingTodos.length == 1 ? 'todo' : 'todos';
    return '${pendingTodos.length} pending $suffix, ${completedTodos.length} completed';
  }

  @computed
  ObservableList<Todo> get visibleTodos {
    switch (filter) {
      case VisibilityFilter.pending:
        return pendingTodos;
      case VisibilityFilter.completed:
        return completedTodos;
      default:
        return todos;
    }
  }

  @computed
  bool get canRemoveAllCompleted =>
      hasCompletedTodos && filter != VisibilityFilter.pending;

  @computed
  bool get canMarkAllCompleted =>
      hasPendingTodos && filter != VisibilityFilter.completed;

  // ...

}
```

**Actions**

Actions are the semantic operations that mutate the observable state. They also
create a transaction-boundary to ensure all notifications are sent out only at
the end of an action. In our case, we have a few actions such as:

```dart
class TodoList = _TodoList with _$TodoList;

abstract class _TodoList with Store {
  // ...

  @action
  void addTodo(String description) {
    final todo = Todo(description);
    todos.add(todo);
  }

  @action
  void removeTodo(Todo todo) {
    todos.removeWhere((x) => x == todo);
  }



  @action
  void changeFilter(VisibilityFilter filter) => this.filter = filter;

  @action
  void removeCompleted() {
    todos.removeWhere((todo) => todo.done);
  }

  @action
  void markAllAsCompleted() {
    for (final todo in todos) {
      todo.done = true;
    }
  }

  // ...
}
```

### The power of co-location

Here is the complete `TodoList` with all the observable, computed properties and
the actions.

```dart
class TodoList = _TodoList with _$TodoList;

abstract class _TodoList with Store {
  @observable
  ObservableList<Todo> todos = ObservableList<Todo>();

  @observable
  VisibilityFilter filter = VisibilityFilter.all;

  @computed
  ObservableList<Todo> get pendingTodos =>
      ObservableList.of(todos.where((todo) => todo.done != true));

  @computed
  ObservableList<Todo> get completedTodos =>
      ObservableList.of(todos.where((todo) => todo.done == true));

  @computed
  bool get hasCompletedTodos => completedTodos.isNotEmpty;

  @computed
  bool get hasPendingTodos => pendingTodos.isNotEmpty;

  @computed
  String get itemsDescription {
    if (todos.isEmpty) {
      return "There are no Todos here. Why don't you add one?.";
    }

    final suffix = pendingTodos.length == 1 ? 'todo' : 'todos';
    return '${pendingTodos.length} pending $suffix, ${completedTodos.length} completed';
  }

  @computed
  ObservableList<Todo> get visibleTodos {
    switch (filter) {
      case VisibilityFilter.pending:
        return pendingTodos;
      case VisibilityFilter.completed:
        return completedTodos;
      default:
        return todos;
    }
  }

  @computed
  bool get canRemoveAllCompleted =>
      hasCompletedTodos && filter != VisibilityFilter.pending;

  @computed
  bool get canMarkAllCompleted =>
      hasPendingTodos && filter != VisibilityFilter.completed;

  @action
  void addTodo(String description) {
    final todo = Todo(description);
    todos.add(todo);
  }

  @action
  void removeTodo(Todo todo) {
    todos.removeWhere((x) => x == todo);
  }

  @action
  void changeFilter(VisibilityFilter filter) => this.filter = filter;

  @action
  void removeCompleted() {
    todos.removeWhere((todo) => todo.done);
  }

  @action
  void markAllAsCompleted() {
    for (final todo in todos) {
      todo.done = true;
    }
  }
}

```

Note that all of the details of the store are together. This is an important
benefit of MobX: it keeps all the state-related code together. _**Co-location**_
is an important virtue and adds lot of value as you build more complex apps with
sophisticated state-management.

Co-location also helps in _readability_ of your code. By choosing
_Domain-specific_ names for your properties, actions and stores, there will be a
clear mapping between the Business-domain and state code.

## Connecting the UI

Now that the state has been defined, the UI becomes a natural, and _visual_
extension of the Store. In case of MobX, we can sprinkle the _Widget-tree_ with
as many `Observer` widgets as needed. You can observe as little as you want or
as much as needed!

> **MobX** is smart enough to know what you are observing and automatically
> starts tracking it. There is no extra work needed on your part. This results
> in friction-free code and makes it a joy to use `Observer`.

At the highest level, this is what your widget tree looks like:

```dart
class TodoExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) => Provider<TodoList>(
      builder: (_) => TodoList(),
      child: Scaffold(
          appBar: AppBar(
            title: const Text('Todos'),
          ),
          body: Column(
            children: <Widget>[
              AddTodo(),
              ActionBar(),
              Description(),
              TodoListView()
            ],
          )));
}

```

We are supplying the state using a `Provider<T>`. This comes from the

<PubBadge name="provider" /> package and makes it easier to access the store in
all of the children. Since the state is kept outside of the `Widget`, this is
also called _**"Lifting the State"**_, which makes the root Widget a
`StatelessWidget`. **_Line#1_** lifts the state with the `Provider<TodoList>`.
Each child accesses the store (aka state) using the `Provider.of<TodoList>()`
passing the `BuildContext` as argument.

:::tip **The Provider package**

We strongly recommend the use of `Provider<T>` to supply the state of your
`Widget` and also for the application. Lifting the state via a `Provider` makes
it convenient and avoids using globals to track your store. It also comes with
several conveniences that makes it noteworthy. This is part of the

<PubBadge name="provider" /> package and definitely worth using in your apps. :::

Each of the children in the root-Widget is an `Observer`. Let's take the
simplest of all the children: `Description`.

**Description**

```dart
class Description extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final list = Provider.of<TodoList>(context);

    return Observer(
        builder: (_) => Padding(
            padding: const EdgeInsets.all(8),
            child: Text(
              list.itemsDescription,
              style: const TextStyle(fontWeight: FontWeight.bold),
            )));
  }
}
```

The _builder-function_ passed into `Observer` monitors all observables
referenced inside. In this case, the `list.itemsDescription` is referenced. The
act of reading this observable is a hint to MobX to start tracking. Anytime it
changes, the `Observer` will rebuild the widget. Notice that there is no extra
work needed from your side! Using the `Provider.of<TodoList>(context)`, we get
access to the `list`.

**TodoListView**

The `TodoListView` is yet another `Observer`, in particular of the
`list.visibleTodos`

```dart
class TodoListView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final list = Provider.of<TodoList>(context);

    return Observer(
        builder: (_) => Flexible(
              child: ListView.builder(
                  itemCount: list.visibleTodos.length,
                  itemBuilder: (_, index) {
                    final todo = list.visibleTodos[index];
                    return Observer(
                        builder: (_) => CheckboxListTile(
                              controlAffinity: ListTileControlAffinity.leading,
                              value: todo.done,
                              onChanged: (flag) => todo.done = flag,
                              title: Row(
                                children: <Widget>[
                                  Expanded(
                                      child: Text(
                                    todo.description,
                                    overflow: TextOverflow.ellipsis,
                                  )),
                                  IconButton(
                                    icon: const Icon(Icons.delete),
                                    onPressed: () => list.removeTodo(todo),
                                  )
                                ],
                              ),
                            ));
                  }),
            ));
  }
}
```

**AddTodo**

Similarly, here is the `AddTodo` widget that fires the `list.addTodo` action.

```dart
class AddTodo extends StatelessWidget {
  final _textController = TextEditingController(text: '');

  @override
  Widget build(BuildContext context) {
    final list = Provider.of<TodoList>(context);

    return TextField(
      autofocus: true,
      decoration: const InputDecoration(
          labelText: 'Add a Todo', contentPadding: EdgeInsets.all(8)),
      controller: _textController,
      textInputAction: TextInputAction.done,
      onSubmitted: (String value) {
        list.addTodo(value);
        _textController.clear();
      },
    );
  }
}

```

**ActionBar**

And finally we have the `ActionBar` that contains the radio-buttons to select a
filter. It also has the buttons to mark all todos and remove the completed ones.

Notice that _there is no business logic here_. Most of the state of the UI is
controlled by _computed-properties_. This is an important modeling tip that you
can use in state-management and even unit-testing!

```dart
class ActionBar extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final list = Provider.of<TodoList>(context);

    return Column(children: <Widget>[
      Observer(
        builder: (_) => Column(
              children: <Widget>[
                RadioListTile(
                    dense: true,
                    title: const Text('All'),
                    value: VisibilityFilter.all,
                    groupValue: list.filter,
                    onChanged: (filter) => list.filter = filter),
                RadioListTile(
                    dense: true,
                    title: const Text('Pending'),
                    value: VisibilityFilter.pending,
                    groupValue: list.filter,
                    onChanged: (filter) => list.filter = filter),
                RadioListTile(
                    dense: true,
                    title: const Text('Completed'),
                    value: VisibilityFilter.completed,
                    groupValue: list.filter,
                    onChanged: (filter) => list.filter = filter),
              ],
            ),
      ),
      Observer(
          builder: (_) => ButtonBar(
                children: <Widget>[
                  ElevatedButton(
                    child: const Text('Remove Completed'),
                    onPressed: list.canRemoveAllCompleted
                        ? list.removeCompleted
                        : null,
                  ),
                  ElevatedButton(
                    child: const Text('Mark All Completed'),
                    onPressed: list.canMarkAllCompleted
                        ? list.markAllAsCompleted
                        : null,
                  )
                ],
              ))
    ]);
  }
}

```

## Summary

import todosGif from './todos.gif';

<img src={todosGif} />

Hope you can see the clarity of expressing the observable-state with
`@observable`, `@computed`, and `@action`. Using the `Observer` widget, you get
an automatically-updating Widget that renders the observable-state. To pass
around the state, we rely on the `Provider<T>` from the

<PubBadge name="provider" /> package.

:::info

See the complete code
[here](https://github.com/mobxjs/mobx.dart/tree/main/mobx_examples/lib/todos).

:::

> ### Comprehensive TodoMVC
>
> A more comprehensive example is available in
> [Brian Egan's Flutter Architecture Samples](https://github.com/brianegan/flutter_architecture_samples/tree/main/mobx),
> that covers few other use cases like localization, persistence, unit-testing,
> navigation, etc. Do take a look
> [here](https://github.com/brianegan/flutter_architecture_samples/tree/main/mobx).
